Skip to content

Add dependency-cooldowns / minimum-artifact-age#3762

Open
rtyley wants to merge 1 commit intoscala-steward-org:mainfrom
guardian:add-dependency-cooldowns
Open

Add dependency-cooldowns / minimum-artifact-age#3762
rtyley wants to merge 1 commit intoscala-steward-org:mainfrom
guardian:add-dependency-cooldowns

Conversation

@rtyley
Copy link
Contributor

@rtyley rtyley commented Nov 28, 2025

@emdash-ie & myself (@rtyley) are co-authors of this work.

This PR adds a new updates.cooldown config option to allow configuring Scala Steward to defer suggesting an update to a new version of a dependency, until that version has survived a 'cooldown' period (eg 7 days).

This intended to be useful to users who want to reduce the risk of updating to a malicious new version of a dependency before it's been examined by their supply-chain security vendor, addressing:

Note that applying a cooldown does not necessarily mean that you will get fewer updates - just that any update offered will be at least as old as specified.

Artifacts that are too recent will get rejected with a TooRecentForCooldown rejection reason.

Internal changes

  • VersionsCache now persists VersionWithFirstSeen rather than Version, allowing us to record when a new version was first seen by this Scala Steward instance (the only simple & reliable way to find out how old a Maven artifact is!). We've added VersionsCacheTest to ensure we can still decode both old and new versions of the cache data.
  • ArtifactUpdateCandidates
    • now stores newerVersionsWithFirstSeen: Nel[VersionWithFirstSeen], so that the candidate versions can be evaluated by age.
    • gains two convenience methods, filterVersions() & filterVersionsWithFirstSeen(), which filter the versions of the update and return an Option[ArtifactUpdateCandidates], with a value of None if no versions are left - this extracts that logic from several places in the Scala Steward codebase (isAllowed, isPinned, isIgnored & scalaLTSFilter)

Config

Right now there's a simple single value that can be configured - this is the cooldown period for all artifacts:

updates.cooldown = {
  minimumAge: "7 days"
}

Future config

Later on, in a subsequent PR, we might allow syntax like this, for example stating that we want all Guardian updates promptly, but all other artifacts only once they're 7 days old:

updates.cooldown = {
  minimumAge: "7 days",
  overrides: [
    { minimumAge: "1 day", [{groupId = "com.gu"}] }
  ]
}

@rtyley rtyley force-pushed the add-dependency-cooldowns branch from bc0d61f to f7dc7c0 Compare November 28, 2025 11:53
@codecov
Copy link

codecov bot commented Nov 28, 2025

Codecov Report

❌ Patch coverage is 96.22642% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.02%. Comparing base (ab567ed) to head (8b69e83).

Files with missing lines Patch % Lines
...org/scalasteward/core/coursier/VersionsCache.scala 92.85% 1 Missing ⚠️
...scala/org/scalasteward/core/update/FilterAlg.scala 80.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3762   +/-   ##
=======================================
  Coverage   90.02%   90.02%           
=======================================
  Files         174      175    +1     
  Lines        5053     5075   +22     
  Branches      451      464   +13     
=======================================
+ Hits         4549     4569   +20     
- Misses        504      506    +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@emdash-ie emdash-ie force-pushed the add-dependency-cooldowns branch from 81ad6e3 to e7c5545 Compare December 4, 2025 16:49
@exoego exoego marked this pull request as ready for review December 4, 2025 20:32
Copy link
Member

@mzuehlke mzuehlke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test are failing and an addition to the default config to document this feature is still missing.
The feature itself looks good 👍

storeRoot / keyEncoder(key) / s"$name.json"
private def jsonFile(key: K): File = {
val file = storeRoot / keyEncoder(key) / s"$name.json"
println(s"file is ${file}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover debug println

@rtyley rtyley marked this pull request as draft January 7, 2026 10:44
@rtyley
Copy link
Contributor Author

rtyley commented Jan 7, 2026

The test are failing and an addition to the default config to document this feature is still missing.
The feature itself looks good 👍

Thanks @mzuehlke - just to be clear, this PR is not yet ready for review, as you've spotted, the tests are still failing and it's not fully implemented - I think @exoego may have set this PR to ready-for-review by mistake!

Looking forward to continuing work on the PR soon.

@rtyley
Copy link
Contributor Author

rtyley commented Jan 8, 2026

@rtyley rtyley force-pushed the add-dependency-cooldowns branch from ac218ca to b8b17c2 Compare January 18, 2026 09:09
rtyley added a commit that referenced this pull request Jan 20, 2026
…des()`

The needs of the Scala Steward codebase don't require a `withNextVersion()`
method on `Update.Single` - just a `supersedes()` method supports the sole
use, which is `PullRequestRepository.getObsoleteOpenPullRequests()` needing
to know if one `Update` can be replaced by another one.

The original `withNewerVersions()` method was added in response to this PR
comment:

#1667 (comment)

...but we reckon replacing it with a slightly different check of the group and
artifact IDs makes for clearer, more explicit code.

This small refactor is extracted from #3762
(originally commit a11ee91), making that PR a little smaller!
rtyley added a commit that referenced this pull request Jan 20, 2026
…des()`

The needs of the Scala Steward codebase don't require a `withNextVersion()`
method on `Update.Single` - just a `supersedes()` method supports the sole
use, which is `PullRequestRepository.getObsoleteOpenPullRequests()` needing
to know if one `Update` can be replaced by another one.

The original `withNewerVersions()` method was added in response to this PR
comment:

#1667 (comment)

...but we reckon replacing it with a slightly different check of the group and
artifact IDs makes for clearer, more explicit code.

This small refactor is extracted from #3762
(originally commit a11ee91), making that PR a little smaller!

Co-authored-by: Emily Bourke <emily.bourke@guardian.co.uk>
@rtyley rtyley force-pushed the add-dependency-cooldowns branch from b8b17c2 to 85d78db Compare January 20, 2026 14:10
@mergify
Copy link
Contributor

mergify bot commented Jan 20, 2026

⚠️ The sha of the head commit of this PR conflicts with #3792. Mergify cannot evaluate rules on this PR. ⚠️

@rtyley rtyley force-pushed the add-dependency-cooldowns branch 2 times, most recently from dd51967 to fbfc634 Compare January 21, 2026 16:06
@rtyley rtyley force-pushed the add-dependency-cooldowns branch 9 times, most recently from 621ed6f to 08aab73 Compare January 29, 2026 15:56
@rtyley rtyley force-pushed the add-dependency-cooldowns branch 2 times, most recently from ca4449e to 77ffaf1 Compare January 29, 2026 16:18
final case class MatchResult(
byArtifactId: List[UpdatePattern],
filteredVersions: List[Version]
filteredVersions: Set[Version]
Copy link
Contributor Author

@rtyley rtyley Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This becomes a Set because it gives better performance on checking if a version was in there, and no remaining consuming code was relying on the ordering.

This PR adds a new `updates.cooldown` config option to allow configuring Scala Steward to defer suggesting an update to a new version of a dependency, until that version has survived a 'cooldown' period (eg 7 days).

This intended to be useful to users who want to reduce the risk of updating to a malicious new version of a dependency before it's been examined by their supply-chain security vendor, addressing:

* scala-steward-org#3757

Note that applying a cooldown does not necessarily mean that you will get _fewer_ updates - just that any update offered will be at least as old as specified.

Artifacts that are too recent will get rejected with a `TooRecentForCooldown` rejection reason.

## Internal changes

* `VersionsCache` now persists `VersionWithFirstSeen` rather than `Version`, allowing us to record when a new version was first seen by this Scala Steward instance (the only simple & reliable way to find out how old a Maven artifact is!). We've added `VersionsCacheTest` to ensure we can still decode both old and new versions of the cache data.

## Config

```
updates.cooldown = {
  minimumAge: "7 days"
}
```

Co-authored-by: Roberto Tyley <roberto.tyley@guardian.co.uk>
@rtyley rtyley force-pushed the add-dependency-cooldowns branch from 77ffaf1 to 8b69e83 Compare January 29, 2026 16:56
@rtyley rtyley marked this pull request as ready for review January 29, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants